#pragma once

#include <iostream>
#include <pthread.h>
#include <queue>
#include "LockGuard.hpp"

const int DefaultCap = 5;

template <class T>
class BlockQueue
{
private:
    bool isEmpty()
    {
        return _bq.empty();
    }

    bool isFull()
    {
        return _bq.size() == _capacity;
    }

public:
    BlockQueue(int capacity = DefaultCap)
        : _capacity(capacity)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_empty, nullptr);
        pthread_cond_init(&_full, nullptr);
    }

    // 生产者
    void push(const T &in)
    {
        // // 访问临界资源需要进行加锁保护
        // pthread_mutex_lock(&_mtx);
        // // 1.先检查当前的临界资源是否满足访问条件
        // // 检查临界资源是否满足访问条件,也是访问临界资源
        // // 所以它也需要在加锁和解锁之间
        // // pthread_cond_wait函数是让线程在特定的条件变量下阻塞等待
        // // 当进行等待时,线程所持有的锁会被自动释放掉.当条件变量满足
        // // 时,线程会被在阻塞挂起的地方被唤醒.线程被唤醒的时候,是在临
        // // 界区中的,pthread_cond_wait会自动帮助线程获取锁
        // while(isFull())
        //     pthread_cond_wait(&_full, &_mtx);
        // // pthread_cond_wait是一个函数,它就可能会调用失败,从而出现
        // // 伪唤醒(临界资源不满足访问条件,却往下进行临界资源的访问)的
        // // 情况,所以需要通过while来保证满足访问临界资源的条件,而不能
        // // 通过if来判断临界资源是否能被访问

        // // 2.访问临界资源(来到这里,临界资源100%是就绪的!)
        // _bq.push(in);
        // // 可以指定特定的唤醒线程的策略,如数据的个数大于容量的一半
        // // if(2 * _bq.size() >= _capacity) pthread_cond_signal(&_empty);
        // // 唤醒线程可以在释放锁之前,也可以在释放锁之后
        // pthread_cond_signal(&_empty);

        // pthread_mutex_unlock(&_mtx);

        // 出了函数的作用域会自动解锁
        LockGuard lockGuard(&_mtx);
        while (isFull())
            pthread_cond_wait(&_full, &_mtx);

        _bq.push(in);
        pthread_cond_signal(&_empty);
    }

    // 消费者
    void pop(T *out)
    {
        // 出了函数的作用域会自动解锁
        LockGuard lockGuard(&_mtx);
        while (isEmpty())
            pthread_cond_wait(&_empty, &_mtx);

        *out = _bq.front();
        _bq.pop();
        pthread_cond_signal(&_full);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_empty);
        pthread_cond_destroy(&_full);
    }

private:
    std::queue<T> _bq;     // 阻塞队列(STL中的容器并不是线程安全的,需要加锁进行保护)
    int _capacity;         // 容量上线
    pthread_mutex_t _mtx;  // 互斥锁保护队列的安全
    pthread_cond_t _empty; // 该条件变量表示bq是否为空
    pthread_cond_t _full;  // 该条件变量表示bq是否为满
};